package com.code.common.annotations;


import com.code.common.util.StringUtil;

import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * 校验枚举值有效性
 * <p>针对某些类的相关字段加入下面@EnumValue进行枚举值的校验，这些类中的枚举的校验方法必须满足返回值类型为Boolean或boolean，并且必须是一个静态的方法</p>
 * <p>直接new对象后调用set设置属性时，此时是不会校验的,这个是Hibernate-validator校验方式，是那注解+切面的方式校验的，主要用于客户端传递过来的参数，而不是自己设置的参数</p>
 *
 * @author tangyifei
 */
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = EnumValue.Validator.class)
public @interface EnumValue {

    String message() default "无效的值";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    Class<? extends Enum<?>> enumClass();

    String enumMethod();

    class Validator implements ConstraintValidator<EnumValue, Object> {

        /**
         * 枚举所属的类型
         */
        private Class<? extends Enum<?>> enumClass;

        /**
         * 枚举方法
         */
        private String enumMethod;

        /**
         * 初始化参数
         *
         * @param enumValue 检验枚举注解
         */
        @Override
        public void initialize(EnumValue enumValue) {
            enumMethod = enumValue.enumMethod();
            enumClass = enumValue.enumClass();
        }

        /**
         * 校验逻辑使用了静态类反射调用验证方法的方式
         *
         * @param value                      检验的值
         * @param constraintValidatorContext 约束校验上下文
         * @return 检验结果
         */
        @Override
        public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) {
            if (null == value) {
                return Boolean.TRUE;
            }

            if (null == enumClass || null == enumMethod) {
                return Boolean.TRUE;
            }

            Class<?> valueClass = value.getClass();

            try {
                Method method = enumClass.getMethod(enumMethod, valueClass);
                if (!Boolean.TYPE.equals(method.getReturnType()) && !Boolean.class.equals(method.getReturnType())) {
                    throw new RuntimeException(StringUtil.formatIfArgs("%s method return is not boolean type in the %s class", enumMethod, enumClass));
                }

                Boolean result = (Boolean) method.invoke(null, value);
                // 返回值为null时我们认为是校验不通过的，按false逻辑走。
                return null == result ? false : result;
            } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
                throw new RuntimeException(e);
            } catch (NoSuchMethodException | SecurityException e) {
                throw new RuntimeException(StringUtil.formatIfArgs("This %s(%s) method does not exist in the %s", enumMethod, valueClass, enumClass), e);
            }
        }

    }
}
